package cn.fintecher.pangolin.service.dataimp.task;

import cn.fintecher.pangolin.common.enums.*;
import cn.fintecher.pangolin.common.exception.BadRequestException;
import cn.fintecher.pangolin.common.model.UserModel;
import cn.fintecher.pangolin.common.utils.BeanUtils;
import cn.fintecher.pangolin.common.utils.ZWDateUtil;
import cn.fintecher.pangolin.entity.elastic.BaseCase;
import cn.fintecher.pangolin.entity.elastic.CaseTransferLog;
import cn.fintecher.pangolin.entity.elastic.CollectorModel;
import cn.fintecher.pangolin.entity.elastic.Strategy;
import cn.fintecher.pangolin.entity.mysql.CaseInfo;
import cn.fintecher.pangolin.entity.mysql.Department;
import cn.fintecher.pangolin.entity.mysql.User;
import cn.fintecher.pangolin.service.dataimp.elastic.BaseCaseElastic;
import cn.fintecher.pangolin.service.dataimp.elastic.CaseTransferLogElastic;
import cn.fintecher.pangolin.service.dataimp.model.StrategyExecuteModel;
import cn.fintecher.pangolin.service.dataimp.repository.CaseInfoRepository;
import cn.fintecher.pangolin.service.dataimp.repository.DepartmentRepository;
import cn.fintecher.pangolin.service.dataimp.repository.UserRepository;
import cn.fintecher.pangolin.service.dataimp.service.BaseService;
import cn.fintecher.pangolin.common.utils.RepositoryUtil;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * @Auther: xiaqun
 * @Description:
 * @Date: 15:04 2019/9/20
 */

@Service("strategyTask")
public class StrategyTask {
    private static final Logger log = LoggerFactory.getLogger(StrategyTask.class);

    @Autowired
    private BaseCaseElastic baseCaseElastic;

    @Autowired
    private DepartmentRepository departmentRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BaseService baseService;

    @Autowired
    private CaseInfoRepository caseInfoRepository;

    @Autowired
    private CaseTransferLogElastic caseTransferLogElastic;

    /**
     * 策略分案
     */
    @Async
    @Transactional
    public CompletableFuture<StrategyExecuteModel> strategyDistribute(Strategy strategy, UserModel userModel) {
        BoolQueryBuilder builder = new BoolQueryBuilder();
        builder.must(QueryBuilders.matchPhraseQuery("strategyId.keyword", strategy.getId()));
        Iterable<BaseCase> iterable = baseCaseElastic.search(builder);
        if (iterable.iterator().hasNext()) {
            List<BaseCase> list = IterableUtils.toList(iterable);
            processDistribute(list, strategy, userModel);
        }
        log.info("策略{}执行完成,共分配{}个案件", strategy.getName(), strategy.getModels().isEmpty() ? 0 : IterableUtils.toList(iterable).size());
        StrategyExecuteModel model = new StrategyExecuteModel();
        model.setStrategy(strategy);
        model.setNumber(IterableUtils.toList(iterable).size());
        return CompletableFuture.completedFuture(model);
    }

    /**
     * 分案逻辑
     */
    private void processDistribute(List<BaseCase> list, Strategy strategy, UserModel userModel) {
        List<CollectorModel> models = strategy.getModels();
        if (models.isEmpty()) {
            log.info("策略{}分案模型为空.............", strategy.getName());
            list.forEach(e -> e.setStrategyId(null));
            baseCaseElastic.saveAll(list);
            return;
        }
        list.sort(Comparator.comparing(BaseCase::getOverdueAmtTotal)); //委案金额正序排列
        Map<String, List<BaseCase>> map;
        if (strategy.getType().equals(StrategyDistributeType.DEPARTMENT)) { //按部门分
            if (strategy.getIsComplex().equals(BaseSelect.NO)) { //设置上限分配
                map = manualDistribute(list, models);
            } else { //综合分配
                map = complexDistribute(list, models);
            }
            distribute(list, map, userModel, 0, strategy.getCaseType());
            //如果还有剩余则做综合分配
            if (!list.isEmpty()) {
                Map<String, List<BaseCase>> last = complexDistribute(list, models);
                distribute(list, last, userModel, 0, strategy.getCaseType());
            }
        } else { //按催收员分
            if (strategy.getIsComplex().equals(BaseSelect.NO)) { //设置上限分配
                map = manualDistribute(list, models);
            } else { //综合分配
                map = complexDistribute(list, models);
            }
            distribute(list, map, userModel, 1, strategy.getCaseType());
            //如果还有剩余则做综合分配
            if (!list.isEmpty()) {
                Map<String, List<BaseCase>> last = complexDistribute(list, models);
                distribute(list, last, userModel, 1, strategy.getCaseType());
            }
        }
    }

    /**
     * 分案逻辑:
     * 分案分为按上限分配和综合分配:
     * 分配前将待分配案件集合按委案金额从小到大排列
     * 新建一个map对象用于存放分配好的案件对象,key为用户或部门的ID,value为待分配案件集合
     * 一.上限分配:
     *  判断要分配的用户或部门是否只有1个:
     *  如果是: 则按照设置的上限值进行分配,方法为:循环0-该值,如果被2整除余0则取待分配集合的第一个对象,如果余1则取待分配集合最后一个对象.取对象后将该对象从待分配案件集合中删除.
     *  如果不是: 循环每个要分配的用户或部门,再重复上述操作.
     * 二.综合分配:
     *  判断要分配的用户或部门是否只有1个:
     *  如果是: 则将到分配的案件全部分给该用户或部门.
     *  如果不是:
     *  1. 计算出每个部门或用户分案的平均值
     *  2. 计算出所有待分配案件委案金额的平均值
     *  3. 循环每个部门或用户,按照设置上限时的分配方法循环0-平均案件数:
     *     1)如果分配数量达到平均值,则该部门或用户分配完成
     *     2)如果分配为达到平均值,但分配的案件的委案金额总数超过金额平均值,则该部门或用户分配完成
     *     3)如果分配完成会还剩余没有分配完的案件，则递归执行上述操作
     */

    /**
     * 设置上限分案
     */
    private Map<String, List<BaseCase>> manualDistribute(List<BaseCase> baseCases, List<CollectorModel> models) {
        Map<String, List<BaseCase>> map = new HashMap<>();
        if (models.size() == 1) { //只有一个 直接分配
            CollectorModel collectorModel = models.get(0);
            List<BaseCase> baseCaseList = cycleDistribute(collectorModel.getNumber(), baseCases);
            List<BaseCase> list = new ArrayList<>(baseCaseList);
            map.put(collectorModel.getId(), list);
        } else { //分配多个
            for (CollectorModel model : models) {
                List<BaseCase> baseCaseList = cycleDistribute(model.getNumber(), baseCases);
                List<BaseCase> list = new ArrayList<>(baseCaseList);
                map.put(model.getId(), list);
            }
        }
        return map;
    }

    /**
     * 综合分配
     */
    private Map<String, List<BaseCase>> complexDistribute(List<BaseCase> baseCases, List<CollectorModel> models) {
        Map<String, List<BaseCase>> map = new HashMap<>();
        if (models.size() == 1) { //只有一个 全部分配
            CollectorModel collectorModel = models.get(0);
            List<BaseCase> list = new ArrayList<>(baseCases);
            map.put(collectorModel.getId(), list);
            baseCases.clear();
        } else { //分配多个
            processComplex(baseCases, models, map);
        }
        return map;
    }

    /**
     * 综合多分案
     */
    private void processComplex(List<BaseCase> baseCases, List<CollectorModel> models, Map<String, List<BaseCase>> map) {
        int avgNumber = (int) Math.ceil((double) baseCases.size() / (double) models.size()); //平均案件数
        double amt = 0d;
        for (BaseCase baseCase : baseCases) {
            amt = amt + baseCase.getOverdueAmtTotal();
        }
        double avgAmt = amt / (double) baseCases.size(); //平均案件金额
        for (CollectorModel model : models) {
            List<BaseCase> list = complexCycleDistribute(baseCases, avgNumber, avgAmt);
            if (map.containsKey(model.getId())) {
                List<BaseCase> caseList = map.get(model.getId());
                caseList.addAll(list);
                map.put(model.getId(), caseList);
            } else {
                map.put(model.getId(), list);
            }
        }
        if (!baseCases.isEmpty()) {
            processComplex(baseCases, models, map);
        }
    }

    /**
     * 上限循环分配
     */
    private List<BaseCase> cycleDistribute(int number, List<BaseCase> baseCases) {
        List<BaseCase> list = new ArrayList<>();
        for (int i = 0; i < number; i++) {
            if (baseCases.isEmpty()) {
                break;
            }
            if (i % 2 == 0) { //偶数时
                BaseCase baseCase = baseCases.get(0);
                list.add(baseCase);
                baseCases.remove(baseCase);
            } else { //奇数时
                BaseCase baseCase = baseCases.get(baseCases.size() - 1);
                list.add(baseCase);
                baseCases.remove(baseCase);
            }
        }
        return list;
    }

    /**
     * 综合循环分配
     */
    private List<BaseCase> complexCycleDistribute(List<BaseCase> baseCases, int avgNumber, double avgAmt) {
        List<BaseCase> list = new ArrayList<>();
        int nowNumber = 0; //当前案件数
        double nowAmt = 0d; //当前案件金额
        if (nowNumber >= avgNumber) { //当前案件数大于等于平局案件数
            return list;
        } else { //当前案件数小于平均案件数
            if (nowAmt >= avgAmt) { //当前案件金额大于等于平均案件金额
                return list;
            }
        }
        for (int i = 0; i < avgNumber; i++) {
            if (baseCases.isEmpty()) {
                break;
            }
            if (i % 2 == 0) { //偶数时
                BaseCase baseCase = baseCases.get(0);
                list.add(baseCase);
                baseCases.remove(baseCase);
                nowNumber++;
                nowAmt += baseCase.getOverdueAmtTotal();
            } else { //奇数时
                BaseCase baseCase = baseCases.get(baseCases.size() - 1);
                list.add(baseCase);
                baseCases.remove(baseCase);
                nowNumber++;
                nowAmt += baseCase.getOverdueAmtTotal();
            }
        }
        return list;
    }

    /**
     * 分配
     */
    private void distribute(List<BaseCase> baseCases, Map<String, List<BaseCase>> map, UserModel userModel, int flag, CaseType caseType) {
        List<CaseInfo> caseInfos = new ArrayList<>();
        List<CaseTransferLog> caseTransferLogList = new ArrayList<>();
        List<BaseCase> updateStatus = new ArrayList<>();
        try {
            for (Map.Entry<String, List<BaseCase>> entry : map.entrySet()) {
                List<BaseCase> list = entry.getValue();
                if (flag == 0) { //按部门分
                    Department department = departmentRepository.findById(entry.getKey()).orElseThrow(() -> new BadRequestException(null, "department", "department.is.null"));
                    for (BaseCase baseCase : list) {
                        CaseInfo caseInfo = new CaseInfo();
                        BeanUtils.copyPropertiesIgnoreNull(baseCase, caseInfo);
                        String outUserId = (Objects.isNull(caseInfo.getCurrentCollector()) ? null : caseInfo.getCurrentCollector().getId());
                        caseInfo.setDepartment(department);
                        caseInfo.setAddress(baseCase.getHomeAddr());
                        caseInfo.setCaseDataStatus(null);
                        caseInfo.setPublicCaseFlag(PublicCaseFlag.NO_PUBLIC_CASE);
                        caseInfo.setCaseType(caseType);
                        caseInfo.setStrategyId(null);
                        caseInfo.setLastContactResult(null);
                        caseInfo.setLastContactDate(null);
                        caseInfo.setOperator(userModel.getRealName());
                        caseInfo.setOperatorTime(ZWDateUtil.getNowDateTime());
                        caseInfos.add(caseInfo);
                        caseTransferLogList.add(baseService.createCaseTranferLog(baseCase.getId(), "策略分案:分案到【".concat(department.getName()).concat("】"), userModel, outUserId, null, baseCase.getLeftAmt()));
                        baseCase.setIsDistribute(BaseSelect.YES);
                        baseCase.setStrategyId(null);
                        updateStatus.add(baseCase);
                    }
                } else { //按催收员分
                    User user = userRepository.findById(entry.getKey()).orElseThrow(() -> new BadRequestException(null, "department", "user.is.null"));
                    for (BaseCase baseCase : list) {
                        CaseInfo caseInfo = new CaseInfo();
                        BeanUtils.copyPropertiesIgnoreNull(baseCase, caseInfo);
                        String outUserId = (Objects.isNull(caseInfo.getCurrentCollector()) ? null : caseInfo.getCurrentCollector().getId());
                        caseInfo.setCurrentCollector(user);
                        caseInfo.setAddress(baseCase.getHomeAddr());
                        caseInfo.setPublicCaseFlag(PublicCaseFlag.NO_PUBLIC_CASE);
                        caseInfo.setCaseDataStatus(CaseDataStatus.IN_POOL);
                        caseInfo.setLastContactResult(null);
                        caseInfo.setLastContactDate(null);
                        caseInfo.setOperator(userModel.getRealName());
                        caseInfo.setCaseType(caseType);
                        caseInfo.setStrategyId(null);
                        caseInfo.setOperatorTime(ZWDateUtil.getNowDateTime());
                        caseInfos.add(caseInfo);
                        if (!StringUtils.equals(outUserId, user.getId())) {
                            caseTransferLogList.add(baseService.createCaseTranferLog(baseCase.getId(), "策略分案:分案到【".concat(user.getRealName()).concat("】"), userModel, outUserId, user.getId(), baseCase.getLeftAmt()));
                        }
                        baseCase.setIsDistribute(BaseSelect.YES);
                        baseCase.setStrategyId(null);
                        updateStatus.add(baseCase);
                    }
                }
            }
            //保存生成案件
            if (!caseInfos.isEmpty()) {
                RepositoryUtil.saveData(caseInfoRepository, caseInfos);
            }
            //保存流转记录
            if (!caseTransferLogList.isEmpty()) {
                RepositoryUtil.saveData(caseTransferLogElastic, caseTransferLogList);
            }
            //保存改变状态的导入案件
            if (!updateStatus.isEmpty()) {
                RepositoryUtil.saveData(baseCaseElastic, updateStatus);
            }
            //处理未分配完的案件
            if (!baseCases.isEmpty()) {
                baseCases.forEach(e -> e.setStrategyId(null));
                RepositoryUtil.saveData(baseCaseElastic, baseCases);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            if (!baseCases.isEmpty()) {
                baseCases.forEach(el -> {
                    el.setIsDistribute(BaseSelect.NO);
                    el.setStrategyId(null);
                });
                RepositoryUtil.saveData(baseCaseElastic, baseCases);
            }
            if (!updateStatus.isEmpty()) {
                updateStatus.forEach(el -> {
                    el.setStrategyId(null);
                    el.setIsDistribute(BaseSelect.NO);
                });
                RepositoryUtil.saveData(baseCaseElastic, updateStatus);
            }
        }
    }
}
